﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using thinger.DataConvertLib;

namespace thinger.MTHModbusLib
{
    public class ModbusTCP
    {
        //建立链接对象
        private Socket socket;
        //发送超时时间
        private int SendTimeout { get; set; } = 2000;

        //接收超时时间
        private int ReceiveTimeout { get; set; } = 2000;

        //最大等待时间
        private int MaxWaitTime { get; set; } = 10;

        //睡眠的时间
        private int SleepTime { get; set; } = 1000;

        //单元标识
        public byte SlaveId { get; set; } = 0x01;

        //建立锁对象
        private SimpleHybridLock simpleHybridLock = new SimpleHybridLock();


        /// <summary>
        /// 连接
        /// </summary>
        /// <param name="ipAdress">ip地址</param>
        /// <param name="post">端口号</param>
        /// <returns></returns>
        public bool Connect(String ipAdress, int post)
        {
            socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            //设置发送超时时间
            socket.SendTimeout = SendTimeout;
            //设置接收超时时间
            socket.ReceiveTimeout = ReceiveTimeout;
            try
            {
                //没有连接就连接
                if (!socket.Connected)
                {
                    socket.Connect(ipAdress, post);
                    return true;
                }
                return false;
            }
            catch (Exception)
            {

                return false;
            }

        }
        /// <summary>
        /// 断开连接
        /// </summary>
        public void Disconnect()
        {
            if (socket.Connected)
            {
                socket?.Dispose();

            }
            socket?.Close();
        }
        /// <summary>
        /// 发送和接收
        /// </summary>
        /// <param name="send">发生数据</param>
        /// <param name="receive">接收数据</param>
        public bool SendAndReceive(byte[] send, ref byte[] receive)
        {
            //加锁
            simpleHybridLock.Enter();

            //计数器
            int time = 0;

            //数据的保存
            byte[] data = new byte[1024];

            //数据流
            MemoryStream memoryStream = new MemoryStream();

            try
            {
                //发送报文
                socket.Send(send, send.Length, SocketFlags.None);

                //接收报文
                while (true)
                {
                    //睡眠
                    Thread.Sleep(SleepTime);
                    //缓存区存在数据
                    if (socket.Available > 0)
                    {
                        //读取数据到data
                        socket.Receive(data, SocketFlags.None);
                        //将数据放在MemoryStream
                        memoryStream.Write(data, 0, data.Length);
                    }
                    else
                    {
                        time += 1;
                        //memoryStream有数据
                        if (memoryStream.Length > 0)
                        {
                            break;
                        }
                        else if (time > MaxWaitTime) //超出最大等待时间
                        {
                            return false;
                        }
                    }
                }
                receive = memoryStream.ToArray();
                return true;
            }
            catch (Exception)
            {

                return false;
            }
            finally
            {
                //释放锁
                simpleHybridLock.Leave();
            }



        }
        /// <summary>
        /// 01H读取输出线圈
        /// </summary>
        /// <param name="start">开始地址</param>
        /// <param name="length">线圈长度（也是线圈个数）</param>
        /// <returns></returns>
        public byte[] ReadOutputCoils(ushort start, ushort length)
        {
            //发送报文：事务标识2+协议标识2+长度2+单元标志1+功能码1+开始地址2+长度2
            ByteArray byteArray = new ByteArray();

            //事务标识 + 协议标识
            byteArray.Add(0x00, 0x00, 0x00, 0x00);

            //长度 + 单元标识 + 功能码
            byteArray.Add(0x00, 0x06, SlaveId, 0x01);

            //开始地址
            byteArray.Add(start);

            //长度
            byteArray.Add(length);

            byte[] receive = null;

            //数据长度
            int byteLength = length % 8 == 0 ? length / 8 : length / 8 + 1;

            //发送并处理接收报文
            if (SendAndReceive(byteArray.Array(), ref receive))
            {
                //返回报文：事务标识2 + 协议标识2 + 长度2 + 单位标识1 + 功能码1 + 字节计数1 + 数据
                if (receive.Length == 9 + byteLength)
                {
                    if (receive[6] == SlaveId && receive[7] == 0x01 && receive[8] == byteLength)
                    {
                        byte[] data = new byte[byteLength];

                        Array.Copy(receive, 9, data, 0, byteLength);

                        return data;
                    }
                }

            }
            return null;
        }
        /// <summary>
        /// 02H读取输入线圈
        /// </summary>
        /// <param name="start">开始地址</param>
        /// <param name="length">线圈长度</param>
        /// <returns></returns>
        public byte[] ReadInputCoils(ushort start, ushort length)
        {
            //发送报文：事务标识+协议标识+长度+单元标志+功能码+开始地址+长度
            ByteArray byteArray = new ByteArray();

            //事务标识 + 协议标识
            byteArray.Add(0x00, 0x00, 0x00, 0x00);

            //长度 + 单元标识 + 功能码
            byteArray.Add(0x00, 0x06, SlaveId, 0x02);

            //开始地址
            byteArray.Add(start);

            //长度
            byteArray.Add(length);

            byte[] receive = null;

            //线圈长度(一个字节等于8位)
            int byteLength = length % 8 == 0 ? length / 8 : length / 8 + 1;

            //发送并处理接收报文
            if (SendAndReceive(byteArray.Array(), ref receive))
            {
                //返回报文：事务标识2 + 协议标识2 + 长度2 + 单位标识1 + 功能码1 + 字节计数1 + 数据
                if (receive.Length == 9 + byteLength)
                {
                    if (receive[6] == SlaveId && receive[7] == 0x02 && receive[8] == byteLength)
                    {
                        byte[] data = new byte[byteLength];

                        Array.Copy(receive, 9, data, 0, byteLength);

                        return data;
                    }
                }

            }
            return null;
        }
        /// <summary>
        /// 03H读取输出寄存器
        /// </summary>
        /// <param name="start">寄存器开始地址</param>
        /// <param name="length">寄存器长度</param>
        /// <returns></returns>
        public byte[] ReadOutputRegisters(ushort start, ushort length)
        {
            //发送报文：事务标识 + 协议标识 + 长度 + 单元标识 + 功能码 + 寄存器开始地址 + 寄存器长度
            ByteArray byteArray = new ByteArray();
            //事务标识 + 协议标识
            byteArray.Add(0x00, 0x00, 0x00, 0x00);
            //长度 + 单元标识 + 功能码
            byteArray.Add(0x00, 0x06, SlaveId, 0x03);

            //寄存器开始地址
            byteArray.Add(start);
            //寄存器长度
            byteArray.Add(length);

            //返回数据的长度：byte
            int byteLength = length * 2;//1个寄存器等于2个字节

            byte[] receive = null;

            //发送并处理接收报文
            if (SendAndReceive(byteArray.Array(), ref receive))
            {
                //返回报文：事务标识2 + 协议标识2 + 长度2 + 单位标识1 + 功能码1 + 字节计数1 + 数据
                if (receive.Length == 9 + byteLength)
                {
                    if (receive[6] == SlaveId && receive[7] == 0x03 && receive[8] == byteLength)
                    {
                        byte[] data = new byte[byteLength];

                        Array.Copy(receive, 9, data, 0, byteLength);

                        return data;
                    }
                }

            }
            return null;
        }
        /// <summary>
        /// 04H读取输入寄存器
        /// </summary>
        /// <param name="start">寄存器开始地址</param>
        /// <param name="length">寄存器长度</param>
        /// <returns></returns>
        public byte[] ReadInputRegisters(ushort start, ushort length)
        {
            //发送报文：事务标识 + 协议标识 + 长度 + 单元标识 + 功能码 + 寄存器开始地址 + 寄存器长度
            ByteArray byteArray = new ByteArray();
            //事务标识 + 协议标识
            byteArray.Add(0x00, 0x00, 0x00, 0x00);
            //长度 + 单元标识 + 功能码
            byteArray.Add(0x00, 0x06, SlaveId, 0x04);

            //寄存器开始地址
            byteArray.Add(start);
            //寄存器长度
            byteArray.Add(length);

            //返回数据的长度：byte
            int byteLength = length * 2;//1个寄存器等于2个字节

            byte[] receive = null;

            //发送并处理接收报文
            if (SendAndReceive(byteArray.Array(), ref receive))
            {
                //返回报文：事务标识2 + 协议标识2 + 长度2 + 单位标识1 + 功能码1 + 字节计数1 + 数据
                if (receive.Length == 9 + byteLength)
                {
                    if (receive[6] == SlaveId && receive[7] == 0x04 && receive[8] == byteLength)
                    {
                        byte[] data = new byte[byteLength];

                        Array.Copy(receive, 9, data, 0, byteLength);

                        return data;
                    }
                }

            }
            return null;
        }
        /// <summary>
        /// 05H写入单个输出线圈
        /// </summary>
        /// <param name="start">线圈开始地址</param>
        /// <param name="value">数据</param>
        /// <returns></returns>
        public bool WriteOutputCoil(ushort start, bool value)
        {
            //发送报文：事务标识 + 协议标识 + 长度 + 单元标识 + 功能码 + 线圈开始地址 + 线圈数据
            ByteArray byteArray = new ByteArray();
            //事务标识 + 协议标识
            byteArray.Add(0x00, 0x00, 0x00, 0x00);
            //长度 + 单元标识 + 功能码
            byteArray.Add(0x00, 0x06, SlaveId, 0x05);
            //线圈开始地址
            byteArray.Add(start);

            //线圈数据
            byteArray.Add(value ? (byte)0xFF : (byte)0x00, 0x00);

            byte[] receive = null;
            //发送并处理接收报文
            if (SendAndReceive(byteArray.Array(), ref receive))
            {
                //返回报文：事务标识 + 协议标识 + 长度 + 单位标识 + 功能码 + 开始线圈地址 + 数据
                if (receive.Length == byteArray.Array().Length)
                {
                    if (receive[6] == SlaveId && receive[7] == 0x05)
                    {
                        return true;
                    }
                }

            }
            return false;
        }
        /// <summary>
        /// 06H配置单个输出寄存器
        /// </summary>
        /// <param name="start">寄存器的开始地址</param>
        /// <param name="data">寄存器数据</param>
        /// <returns></returns>
        public bool WriteOutputRegister(ushort start, byte[] data)
        {
            //发送报文：事务标识 + 协议标识 + 长度 + 单元标识 + 功能码 + 寄存器开始地址 + 寄存器数据
            ByteArray byteArray = new ByteArray();
            //事务标识 + 协议标识
            byteArray.Add(0x00, 0x00, 0x00, 0x00);
            //长度 + 单元标识 + 功能码
            byteArray.Add(0x00, 0x06, SlaveId, 0x06);
            //寄存器开始地址
            byteArray.Add(start);
            //数据
            byteArray.Add(data);

            byte[] receive = null;

            //发送并处理接收报文
            if (SendAndReceive(byteArray.Array(), ref receive))
            {
                //返回报文：事务标识 + 协议标识 + 长度 + 单位标识 + 功能码 + 开始线圈地址 + 数据
                if (receive.Length == byteArray.Array().Length)
                {
                    if (receive[6] == SlaveId && receive[7] == 0x06)
                    {
                        return true;
                    }
                }

            }
            return false;

        }
        /// <summary>
        /// 0FH配置多个输出线圈
        /// </summary>
        /// <param name="start"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        public bool WriteOutputCoils(ushort start, bool[] value)
        {
            //发送报文：事务标识2 + 协议标识2 + 长度 + 单位标识1 + 功能码1 + 线圈开始地址2 + 线圈数量2 + 字节计数1 + 线圈数据
            ByteArray byteArray = new ByteArray();

            //bool[]转换成byte[]
            byte[] bytes = ByteArrayLib.GetByteArrayFromBoolArray(value);

            //事务标识 + 协议标识
            byteArray.Add(0x00, 0x00, 0x00, 0x00);

            //长度
            byteArray.Add((short)(7 + bytes.Length));

            //单位标识 + 功能码
            byteArray.Add(SlaveId, 0x0F);

            //线圈开始地址2 + 线圈数量2 + 字节计数1
            byteArray.Add(start);
            byteArray.Add((short)value.Length);
            byteArray.Add((short)bytes.Length);

            //数据
            byteArray.Add(bytes);

            byte[] receive = null;

            if (SendAndReceive(byteArray.Array(), ref receive))
            {
                //接收报文：事务标识2 + 协议标识2 + 长度 + 单位标识1 + 功能码1 + 线圈开始地址2 + 线圈数量2
                if (receive[6] == SlaveId && receive[7] == 0x0F)
                {
                    return true;
                }
            }
            return false;

        }
        /// <summary>
        /// 10H配置多个输出寄存器
        /// </summary>
        /// <param name="start"></param>
        /// <param name="data"></param>
        /// <returns></returns>
        public bool WriteOutputRegisters(ushort start, byte[] data)
        {
            //发送报文：事务标识 + 协议标识 + 长度 + 单元标识1 + 功能码1+ 寄存器开始地址2 + 寄存器数量2 + 字节计数1 + 数据
            ByteArray byteArray = new ByteArray();

            //事务标识 + 协议标识
            byteArray.Add(0x00, 0x00, 0x00, 0x00);

            //长度
            byteArray.Add(((short)(7 + data.Length)));

            //单元标识1 + 功能码1
            byteArray.Add(SlaveId, 0x10);

            //寄存器开始地址2 + 寄存器数量2 + 字节计数1 + 数据
            byteArray.Add(start);
            byteArray.Add(data.Length % 2 == 0 ? (short)(data.Length / 2) : (short)(data.Length / 2 + 1));
            byteArray.Add((short)data.Length);
            byteArray.Add(data);

            byte[] receive = null;

            if (SendAndReceive(byteArray.Array(), ref receive))
            {
                //接收报文：事务标识2 + 协议标识2 + 长度 + 单位标识1 + 功能码1 + 寄存器开始地址2 + 寄存器数量2
                if (receive[6] == SlaveId && receive[7] == 0x10)
                {
                    return true;
                }
            }
            return false;
        }

        //混合锁
        public sealed class SimpleHybridLock : IDisposable
        {
            //基元用户模式构造使用
            private int m_waiters = 0;
        
            //基元内核模式构造
            private AutoResetEvent m_waiterLock = new AutoResetEvent(false);
  
            public void Enter()
            {
                //指出该线程想要获得锁
                if (Equals(Interlocked.Increment(ref m_waiters), 1))
                {
                    //无竞争，直接返回
                    return;
                }
 
                //另一个线程拥有锁（发生竞争），使这个线程等待
                //线程会阻塞，但不会在CPU上“自旋”，从而节省CPU
                //这里产生较大的性能影响（用户模式与内核模式之间转换）
                //待WaitOne返回后，这个线程拿到锁
                m_waiterLock.WaitOne();
            }
 
            public void Leave()
            {
                //该线程准备释放锁
                if (Equals(Interlocked.Decrement(ref m_waiters), 0))
                {
                    //无线程等待，直接返回
                    return;
                }
 
                //有线程等待则唤醒其中一个
                //这里产生较大的性能影响（用户模式与内核模式之间转换）
                m_waiterLock.Set();
            }
 
            public void Dispose()
            {
                m_waiterLock.Dispose();
            }
        }


    }
}
